/*
 * 文件名：CoordinateHelper.java
 * 版权：Copyright by 深圳市优联物联网有限公司
 * 描述：
 * 修改人：杨杰
 * 修改时间：2017年10月23日
 * 跟踪单号：
 * 修改单号：
 * 修改内容：
 */
package com.bicycle.common.helper;

import java.awt.geom.Point2D;
import java.math.BigDecimal;
import java.util.List;

/**
 * 坐标转换工具类<br/>
 * 球上同一个地理位置的经纬度，在不同的坐标系中，会有少于偏移，国内目前常见的坐标系主要分为三种：<br/>
 * 地球坐标系——WGS84：常见于 GPS 设备，Google 地图等国际标准的坐标体系。<br/>
 * 火星坐标系——GCJ-02：中国国内使用的被强制加密后的坐标体系，高德坐标就属于该种坐标体系。<br/>
 * 百度坐标系——BD-09：百度地图所使用的坐标体系，是在火星坐标系的基础上又进行了一次加密处理。<br/>
 * 
 * @author hncdyj123@163.com
 * @date 2017年5月16日 下午5:31:58
 */
public class CoordinateHelper {
	static double pi = 3.14159265358979324;
	static double a = 6378245.0;
	static double ee = 0.00669342162296594323;
	public final static double x_pi = 3.14159265358979324 * 3000.0 / 180.0;

	private static double metersPerDegree = 2.0 * Math.PI * a / 360.0;
	private static double radiansPerDegree = Math.PI / 180.0;

	/**
	 * gps转百度坐标
	 * 
	 * @param lat 纬度
	 * @param lon 经度
	 * @return 经纬度数组 [0]纬度 [1]经度
	 * @see
	 */
	public static double[] wgs2bd(double lat, double lon) {
		double[] wgs2gcj = wgs2gcj(lat, lon);
		double[] gcj2bd = gcj2bd(wgs2gcj[0], wgs2gcj[1]);
		return gcj2bd;
	}

	/**
	 * GCJ-02转百度坐标系
	 * 
	 * @param lat
	 * @param lon
	 * @return
	 * @see
	 */
	public static double[] gcj2bd(double lat, double lon) {
		double x = lon, y = lat;
		double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
		double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
		double bd_lon = z * Math.cos(theta) + 0.0065;
		double bd_lat = z * Math.sin(theta) + 0.006;
		return new double[] { bd_lat, bd_lon };
	}

	/**
	 * 百度坐标转GPS
	 * 
	 * @param lat 纬度
	 * @param lon 经度
	 * @return 经纬度数组 [0]纬度 [1]经度
	 * @see
	 */
	public static double[] bd2gcj(double lat, double lon) {
		double x = lon - 0.0065, y = lat - 0.006;
		double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
		double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
		double gg_lon = z * Math.cos(theta);
		double gg_lat = z * Math.sin(theta);
		return new double[] { gg_lat, gg_lon };
	}

	/**
	 * GPS转GCJ-02坐标系<br/>
	 * 高德坐标属于该坐标系:GPS转高德<br/>
	 * 
	 * @param lat
	 * @param lon
	 * @return
	 * @see
	 */
	public static double[] wgs2gcj(double lat, double lon) {
		double dLat = transformLat(lon - 105.0, lat - 35.0);
		double dLon = transformLon(lon - 105.0, lat - 35.0);
		double radLat = lat / 180.0 * pi;
		double magic = Math.sin(radLat);
		magic = 1 - ee * magic * magic;
		double sqrtMagic = Math.sqrt(magic);
		dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
		dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
		double mgLat = lat + dLat;
		double mgLon = lon + dLon;
		double[] loc = { mgLat, mgLon };
		return loc;
	}

	/**
	 * GCJ坐标系转GPS
	 * 
	 * @param lat
	 * @param lon
	 * @return
	 * @see
	 */
	public static double[] gcj2wgs(double lat, double lon) {
		double dLat = transformLat(lon - 105.0, lat - 35.0);
		double dLon = transformLon(lon - 105.0, lat - 35.0);
		double radLat = lat / 180.0 * pi;
		double magic = Math.sin(radLat);
		magic = 1 - ee * magic * magic;
		double sqrtMagic = Math.sqrt(magic);
		dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
		dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
		double mgLat = lat - dLat;
		double mgLon = lon - dLon;
		double[] loc = { mgLat, mgLon };
		return loc;
	}

	private static double transformLat(double lat, double lon) {
		double ret = -100.0 + 2.0 * lat + 3.0 * lon + 0.2 * lon * lon + 0.1 * lat * lon + 0.2 * Math.sqrt(Math.abs(lat));
		ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
		ret += (20.0 * Math.sin(lon * pi) + 40.0 * Math.sin(lon / 3.0 * pi)) * 2.0 / 3.0;
		ret += (160.0 * Math.sin(lon / 12.0 * pi) + 320 * Math.sin(lon * pi / 30.0)) * 2.0 / 3.0;
		return ret;
	}

	private static double transformLon(double lat, double lon) {
		double ret = 300.0 + lat + 2.0 * lon + 0.1 * lat * lat + 0.1 * lat * lon + 0.1 * Math.sqrt(Math.abs(lat));
		ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
		ret += (20.0 * Math.sin(lat * pi) + 40.0 * Math.sin(lat / 3.0 * pi)) * 2.0 / 3.0;
		ret += (150.0 * Math.sin(lat / 12.0 * pi) + 300.0 * Math.sin(lat / 30.0 * pi)) * 2.0 / 3.0;
		return ret;
	}

	/**
	 * 获取两个经纬度之间的距离
	 * 
	 * @param lat_a
	 * @param lng_a
	 * @param lat_b
	 * @param lng_b
	 * @return
	 * @see
	 */
	public static double getDistance(double lat_a, double lng_a, double lat_b, double lng_b) {
		double pk = 180 / 3.14169;
		double a1 = lat_a / pk;
		double a2 = lng_a / pk;
		double b1 = lat_b / pk;
		double b2 = lng_b / pk;
		double t1 = Math.cos(a1) * Math.cos(a2) * Math.cos(b1) * Math.cos(b2);
		double t2 = Math.cos(a1) * Math.sin(a2) * Math.cos(b1) * Math.sin(b2);
		double t3 = Math.sin(a1) * Math.sin(b1);
		double tt = Math.acos(t1 + t2 + t3);
		return 6371000 * tt;
	}

	/***
	 * 平面多边形面积
	 * 
	 * @param points Point2D.Double
	 * @return
	 */
	public static double getPolygonArea(List<Point2D.Double> points) {
		double a = 0.0;
		if (points.size() > 2) {
			for (int i = 0; i < points.size(); ++i) {
				int j = (i + 1) % points.size();
				double xi = points.get(i).x * metersPerDegree * Math.cos(points.get(i).y * radiansPerDegree);
				double yi = points.get(i).y * metersPerDegree;
				double xj = points.get(j).x * metersPerDegree * Math.cos(points.get(j).y * radiansPerDegree);
				double yj = points.get(j).y * metersPerDegree;
				a += xi * yj - xj * yi;
			}
		}
		a = BigDecimal.valueOf(Math.abs(a / 2.0)).setScale(1, BigDecimal.ROUND_DOWN).doubleValue();
		return a;
	}

	/**
	 * 判断一个点是否在区域范围内
	 * 
	 * @param point
	 * @param pts
	 * @return
	 * @see
	 */
	public static boolean IsPtInPoly(Point2D.Double point, List<Point2D.Double> pts) {

		int N = pts.size();
		boolean boundOrVertex = true; // 如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
		int intersectCount = 0;// cross points count of x
		double precision = 2e-10; // 浮点类型计算时候与0比较时候的容差
		Point2D.Double p1, p2;// neighbour bound vertices
		Point2D.Double p = point; // 当前点

		p1 = pts.get(0);// left vertex
		for (int i = 1; i <= N; ++i) {// check all rays
			if (p.equals(p1)) {
				return boundOrVertex;// p is an vertex
			}

			p2 = pts.get(i % N);// right vertex
			if (p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)) {// ray is outside of our interests
				p1 = p2;
				continue;// next ray left point
			}

			if (p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)) {// ray is crossing over by the algorithm (common part of)
				if (p.y <= Math.max(p1.y, p2.y)) {// x is before of ray
					if (p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)) {// overlies on a horizontal ray
						return boundOrVertex;
					}

					if (p1.y == p2.y) {// ray is vertical
						if (p1.y == p.y) {// overlies on a vertical ray
							return boundOrVertex;
						} else {// before ray
							++intersectCount;
						}
					} else {// cross point on the left side
						double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;// cross point of y
						if (Math.abs(p.y - xinters) < precision) {// overlies on a ray
							return boundOrVertex;
						}

						if (p.y < xinters) {// before ray
							++intersectCount;
						}
					}
				}
			} else {// special case when ray is crossing through the vertex
				if (p.x == p2.x && p.y <= p2.y) {// p crossing over p2
					Point2D.Double p3 = pts.get((i + 1) % N); // next vertex
					if (p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)) {// p.x lies between p1.x & p3.x
						++intersectCount;
					} else {
						intersectCount += 2;
					}
				}
			}
			p1 = p2;// next ray left point
		}

		if (intersectCount % 2 == 0) {// 偶数在多边形外
			return false;
		} else { // 奇数在多边形内
			return true;
		}

	}

	/**
	 * @Description 计算给定经纬度附近相应公里数的经纬度范围
	 * @param longitude 经度
	 * @param latitude 纬度
	 * @param distince 距离（千米）
	 * @return String 格式：经度最小值-经度最大值-纬度最小值-纬度最大值
	 * @Data 2018.10.26
	 **/
	public static String getNearbyByLongitudeAndLatitudeAndDistince(double longitude, double latitude, double distince) {
		distince = distince / 2.0;
		double r = 6378245; // 地球半径千米
		double lng = longitude;
		double lat = latitude;
		double dlng = 2 * Math.asin(Math.sin(distince / (2 * r)) / Math.cos(lat * Math.PI / 180));
		dlng = dlng * 180 / Math.PI;// 角度转为弧度
		double dlat = distince / r;
		dlat = dlat * 180 / Math.PI;
		double minlat = lat - dlat;
		double maxlat = lat + dlat;
		double minlng = lng - dlng;
		double maxlng = lng + dlng;

		return minlng + "-" + maxlng + "-" + minlat + "-" + maxlat;
	}

}
